////////////////////////////////////////////////////////////////////////////////////////////////////
//
//	Crytek Character Animation source code
//	
//	History:
//	10/9/2004 - Created by Ivo Herzeg <ivo@crytek.de>
//
//  Contains:
//  interface class to all motions  
/////////////////////////////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "CharacterManager.h"
#include "Model.h"
#include "ModelAnimationSet.h"
#include "ModelMesh.h"
#include "LMG.h"
#include "SDI.h"
#include "LoaderDBA.h"
#include "LoaderLMG.h"

// on spu a structure defined in forwardkinematicsspu.cpp is used to hold the needed data
#if !defined(__SPU__)
typedef GlobalAnimationHeaderCAF&	GlobalAnimationHeaderType;
#endif

const char* strEmpty=""; 

CAnimationSet::CAnimationSet()
{
	m_pModel=0;
	m_CharEditMode=0;
}


CAnimationSet::~CAnimationSet()
{
	Clear();
	g_AnimationManager.Unregister(this);
}

void CAnimationSet::Init()
{
	g_AnimationManager.Register(this);
}





const char* CAnimationSet::GetFacialAnimationPathByName(const char* szName)
{
	FacialAnimationSet::iterator itFacialAnim = std::lower_bound(m_facialAnimations.begin(), m_facialAnimations.end(), szName, stl::less_stricmp<const char*>());
	if (itFacialAnim != m_facialAnimations.end() && stl::less_stricmp<const char*>()(szName, *itFacialAnim))
		itFacialAnim = m_facialAnimations.end();
	const char* szPath = (itFacialAnim != m_facialAnimations.end() ? (*itFacialAnim).path.c_str() : 0);
	return szPath;
}

int CAnimationSet::GetNumFacialAnimations()
{
	return m_facialAnimations.size();
}

const char* CAnimationSet::GetFacialAnimationName(int index)
{
	if (index < 0 || index >= (int)m_facialAnimations.size())
		return 0;
	return m_facialAnimations[index].name.c_str();
}




const char* CAnimationSet::GetFacialAnimationPath(int index)
{
	if (index < 0 || index >= (int)m_facialAnimations.size())
		return 0;
	return m_facialAnimations[index].path.c_str();
}

void CAnimationSet::ReuseAnimation(const ModelAnimationHeader& header, const char* szStandupAnimType)
{
	m_arrAnimations.push_back(header);

	int nLocalAnimId2 = m_arrAnimations.size()-1;
	m_AnimationHashMap.InsertValue(header.m_CRC32Name, nLocalAnimId2);
	
	if (szStandupAnimType)
		ProcessStandupAnim(szStandupAnimType, nLocalAnimId2);
}

//////////////////////////////////////////////////////////////////////////
// Loads animation file. Returns the global anim id of the file, or -1 if error
// SIDE EFFECT NOTES:
//  THis function does not put up a warning in the case the animation couldn't be loaded.
//  It returns an error (false) and the caller must process it.
int CAnimationSet::LoadFileCAF(const char* szFilePath, const char* szAnimName)
{
	//NEW RULE: every single CAF file is now an OnDemand streaming animation
	int nAnimId = GetAnimIDByName (szAnimName);
	if (nAnimId != -1)
	{
		int nGlobalAnimID = m_arrAnimations[nAnimId].m_nGlobalAnimId;
		g_pILog->LogWarning("CryAnimation:: Trying to load animation with alias \"%s\" from file \"%s\" into the animation container. Such animation alias already exists and uses file \"%s\". Please use another animation alias.", szAnimName, szFilePath, g_AnimationManager.m_arrGlobalCAF[nGlobalAnimID].GetFilePath());
		return nGlobalAnimID;
	}

	int nGlobalAnimID = g_AnimationManager.CreateGAH_CAF(szFilePath);

	ModelAnimationHeader localAnim; 
	localAnim.m_nGlobalAnimId = nGlobalAnimID;
	localAnim.m_nAssetType=CAF_File;
	localAnim.SetAnimName(szAnimName);
	m_arrAnimations.push_back(localAnim);
	
	StoreAnimName(localAnim.m_CRC32Name, szAnimName);

	int nLocalAnimId2 = m_arrAnimations.size()-1;
	m_AnimationHashMap.InsertValue(localAnim.m_CRC32Name, nLocalAnimId2);

	GlobalAnimationHeaderCAF& rCAF = g_AnimationManager.m_arrGlobalCAF[nGlobalAnimID];
	rCAF.AddRef();

	uint32 IsCreated = rCAF.IsAssetCreated();
	if ( IsCreated==0 )
	{
		//	uint32 IsAimPose = (CryStringUtils::stristr(szAnimName,"AimPoses") != 0);
		//	if (IsAimPose)
		//		CryFatalError("we don't load aim-poses here: %s",szFilePath);
		//--------------------------------------------------------------------------------
		//asset is not created, so let's create by loading the CAF
		assert(rCAF.GetControllersCount()==0);
		uint32 nRootCRC32	= this->m_pModel->m_ModelSkeleton.m_arrModelJoints[0].m_nJointCRC32;
		uint8 status = rCAF.LoadCAF(nRootCRC32);
		if (status)
		{
			rCAF.ClearAssetRequested();
			assert(rCAF.IsAssetLoaded());
			if (Console::GetInst().ca_UseIMG_CAF)
				g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING,	VALIDATOR_FLAG_FILE,rCAF.GetFilePath(),	"Unnecessary Loading of CAF-files: Probably no valid IMG-file availbale" );
		}
	} 
	else 
	{
		//this is only for production mode
		if (Console::GetInst().ca_UseIMG_CAF==0)
		{
			if (rCAF.IsAssetOnDemand()==0)
			{
				//asset is already in a DBA
				uint8 exist = rCAF.DoesExistCAF();  //check if we also have a CAF-file
				if (exist)
				{
					//overload the DBA animation.
					uint32 nRootCRC32	= this->m_pModel->m_ModelSkeleton.m_arrModelJoints[0].m_nJointCRC32;
					uint8 status = rCAF.LoadCAF(nRootCRC32);
					if (status)
					{
						rCAF.ClearAssetRequested();
						assert(rCAF.IsAssetLoaded());
						if (Console::GetInst().ca_UseIMG_CAF)
							g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING,	VALIDATOR_FLAG_FILE,rCAF.GetFilePath(),	"Unnecessary Loading of CAF-files: Probably no valid IMG-file availbale" );
					}
				}
			} 
		}
	}

	CryFixedStringT<256> standupAnimType;
	if (IsStandupAnim(szAnimName, standupAnimType))
	{
		ProcessStandupAnim(standupAnimType.c_str(), nLocalAnimId2);
	}

	return nGlobalAnimID;
}

//////////////////////////////////////////////////////////////////////////
// Loads animation file. Returns the global anim id of the file, or -1 if error
// SIDE EFFECT NOTES:
//  THis function does not put up a warning in the case the animation couldn't be loaded.
//  It returns an error (false) and the caller must process it.
int CAnimationSet::LoadFileAIM(const char* szFilePath, const char* szAnimName)
{
	//NEW RULE: every single AIM file is now an OnDemand streaming animation
	int nAnimId = GetAnimIDByName (szAnimName);
	if (nAnimId != -1)
	{
		int nGlobalAnimID = m_arrAnimations[nAnimId].m_nGlobalAnimId;
		g_pILog->LogWarning("CryAnimation:: Trying to load animation with alias \"%s\" from file \"%s\" into the animation container. Such animation alias already exists and uses file \"%s\". Please use another animation alias.", szAnimName, szFilePath, g_AnimationManager.m_arrGlobalAIM[nGlobalAnimID].GetFilePath());
		return nGlobalAnimID;
	}

	int nGlobalAnimID = g_AnimationManager.CreateGAH_AIM(szFilePath);


	ModelAnimationHeader localAnim; 
	localAnim.m_nGlobalAnimId = nGlobalAnimID;
	localAnim.m_nAssetType=AIM_File;
	localAnim.SetAnimName(szAnimName);
	m_arrAnimations.push_back(localAnim);

	StoreAnimName(localAnim.m_CRC32Name, szAnimName);

	int nLocalAnimId2 = m_arrAnimations.size()-1;
	m_AnimationHashMap.InsertValue(localAnim.m_CRC32Name, nLocalAnimId2);

	GlobalAnimationHeaderAIM& rAIM = g_AnimationManager.m_arrGlobalAIM[nGlobalAnimID];
	rAIM.AddRef();

	uint32 loaded = rAIM.IsAssetCreated();
	if (loaded)
		return nGlobalAnimID;

	//----------------------------------------------------------------------------------------

	CModelSkeleton* pModelSkeleton = &m_pModel->m_ModelSkeleton;
	assert(rAIM.GetControllersCount()==0);
	uint8 status = rAIM.LoadAIM();
	if (status)
	{
		uint32 numAimDB = pModelSkeleton->m_AimDirBlends.size();
		for (uint32 d=0; d<numAimDB; d++)
		{
			const char* strAimIK_Token = pModelSkeleton->m_AimDirBlends[d].m_AnimToken;
			uint32 IsAIM = (CryStringUtils::stristr(szAnimName,strAimIK_Token) != 0);
			if (IsAIM)
			{
				rAIM.ProcessAimPoses(m_pModel,pModelSkeleton->m_AimDirBlends,pModelSkeleton->m_AimIK_Rot,pModelSkeleton->m_AimIK_Pos);
				break;
			}
		}

		uint32 numLookDB = pModelSkeleton->m_LookDirBlends.size();
		for (uint32 d=0; d<numLookDB; d++)
		{
			const char* strLookIK_Token = pModelSkeleton->m_LookDirBlends[d].m_AnimToken;
			uint32 IsLookIK = (CryStringUtils::stristr(szAnimName,strLookIK_Token) != 0);
			if (IsLookIK )
			{
				rAIM.ProcessAimPoses(m_pModel,pModelSkeleton->m_LookDirBlends,pModelSkeleton->m_LookIK_Rot,pModelSkeleton->m_LookIK_Pos);
				break;
			}
		}

	} 
	else
	{
		//Aim-Pose does not exist as file. this is an error
		if ( rAIM.IsAssetNotFound()==0 )
		{
			const char* pPathName=rAIM.GetFilePath();
			g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING,	VALIDATOR_FLAG_FILE,pPathName,	"Failed to load animation CAF file" );
			rAIM.OnAssetNotFound();
		}
	} 

	return nGlobalAnimID;
}





//////////////////////////////////////////////////////////////////////////
// Loads animation file. Returns the global anim id of the file, or -1 if error
// SIDE EFFECT NOTES:
//  THis function does not put up a warning in the case the animation couldn't be loaded.
//  It returns an error (false) and the caller must process it.
int CAnimationSet::LoadFileANM(const char* szFileName, const char* szAnimName, DynArray<CControllerTCB>& m_LoadCurrAnimation, CryCGALoader* pCGA, uint32 unique_model_id )
{
	int nModelAnimId = GetAnimIDByName (szAnimName);
	if (nModelAnimId == -1) 
	{
		int nGlobalAnimID = g_AnimationManager.CreateGAH_CAF(szFileName);

		int nLocalAnimId = m_arrAnimations.size();
		ModelAnimationHeader LocalAnim; 
		LocalAnim.m_nGlobalAnimId = nGlobalAnimID;
		LocalAnim.m_nAssetType=CAF_File;
		LocalAnim.SetAnimName(szAnimName);
		m_arrAnimations.push_back(LocalAnim);

		StoreAnimName(LocalAnim.m_CRC32Name, szAnimName);

		//m_arrAnimByGlobalId.insert (std::lower_bound(m_arrAnimByGlobalId.begin(), m_arrAnimByGlobalId.end(), nGlobalAnimID, AnimationGlobIdPred(m_arrAnimations)), LocalAnimId(nLocalAnimId));
		m_AnimationHashMap.InsertValue(LocalAnim.m_CRC32Name, nLocalAnimId);
		//		m_arrAnimByLocalName.insert (std::lower_bound(m_arrAnimByLocalName.begin(), m_arrAnimByLocalName.end(), szAnimName, AnimationNamePred(m_arrAnimations)), nLocalAnimId);

		g_AnimationManager.m_arrGlobalCAF[nGlobalAnimID].AddRef();

		uint32 loaded = g_AnimationManager.m_arrGlobalCAF[nGlobalAnimID].IsAssetLoaded();
		if (loaded)
		{
			// No asserts on data
			//assert(g_AnimationManager.m_arrGlobalCAF[nGlobalAnimID].m_arrController.size());
		}
		else
		{
			//assert(g_AnimationManager.m_arrGlobalCAF[nGlobalAnimID].m_arrController.size()==0);
			//assert(g_AnimationManager.m_arrGlobalCAF[nGlobalAnimID].m_arrController);
			g_AnimationManager.LoadAnimationTCB(nGlobalAnimID, m_LoadCurrAnimation, pCGA, unique_model_id );
		}

		return nGlobalAnimID;
	}
	else
	{
		int nGlobalAnimID = m_arrAnimations[nModelAnimId].m_nGlobalAnimId;
		g_pILog->LogWarning("CryAnimation:: Trying to load animation with alias \"%s\" from file \"%s\" into the animation container. Such animation alias already exists and uses file \"%s\". Please use another animation alias.",szAnimName,	szFileName,	g_AnimationManager.m_arrGlobalCAF[nGlobalAnimID].GetFilePath());
		return nGlobalAnimID;
	}
}






//-------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------

int CAnimationSet::LoadFileLMG(const char* szFilePath, const char* szAnimName)
{
	int nModelAnimId = GetAnimIDByName (szAnimName);
	if (nModelAnimId == -1) 
	{
		int nGlobalAnimID = g_AnimationManager.CreateGAH_LMG(szFilePath);
		int nLocalAnimId = m_arrAnimations.size();

		ModelAnimationHeader LocalAnim; 
		LocalAnim.m_nGlobalAnimId = nGlobalAnimID;
		LocalAnim.m_nAssetType=LMG_File;
		LocalAnim.SetAnimName(szAnimName);
		m_arrAnimations.push_back(LocalAnim);

		StoreAnimName(LocalAnim.m_CRC32Name, szAnimName);

		m_AnimationHashMap.InsertValue(LocalAnim.m_CRC32Name, nLocalAnimId);

		g_AnimationManager.m_arrGlobalLMG[nGlobalAnimID].AddRef();
		return nGlobalAnimID;
	}
	else
	{

		const ModelAnimationHeader* anim = GetModelAnimationHeader(nModelAnimId);
		if (anim)
		{
			if (anim->m_nAssetType==CAF_File)
			{
				GlobalAnimationHeaderCAF& rCAF = g_AnimationManager.m_arrGlobalCAF[anim->m_nGlobalAnimId];
				g_pILog->LogWarning("CryAnimation:: weird access of CAF \"%s\"",	rCAF.GetFilePath());
				return anim->m_nGlobalAnimId;
			}
			if (anim->m_nAssetType==AIM_File)
			{
				GlobalAnimationHeaderAIM& rAIM = g_AnimationManager.m_arrGlobalAIM[anim->m_nGlobalAnimId];
				g_pILog->LogWarning("CryAnimation:: weird access of AIM \"%s\"",	rAIM.GetFilePath());
				return anim->m_nGlobalAnimId;
			}
			if (anim->m_nAssetType==LMG_File)
			{
				GlobalAnimationHeaderLMG& rLMG = g_AnimationManager.m_arrGlobalLMG[anim->m_nGlobalAnimId];
				g_pILog->LogWarning("CryAnimation:: Trying to load animation with alias \"%s\" from file \"%s\" into the animation container. Such animation alias already exists and uses file \"%s\". Please use another animation alias.",szAnimName, szFilePath,	rLMG.GetFilePath());
				return anim->m_nGlobalAnimId;
			}

		}

		return -1;
	}

}


int CAnimationSet::CreateOrSetAnimationLMG( const char* animationName, int globalAnimationLmgId )
{
	int localAnimationId = GetAnimIDByName(animationName);
	const bool animationAlreadyLoaded = ( localAnimationId != -1 );
	if ( animationAlreadyLoaded )
	{
		ModelAnimationHeader& animationHeader = m_arrAnimations[ localAnimationId ];
		const int oldGlobalAnimationId = animationHeader.m_nGlobalAnimId;
		const bool changingAnimationGlobalId = ( oldGlobalAnimationId != globalAnimationLmgId );
		if ( changingAnimationGlobalId )
		{
			animationHeader.m_nGlobalAnimId = globalAnimationLmgId;

			g_AnimationManager.m_arrGlobalLMG[ globalAnimationLmgId ].AddRef();
			g_AnimationManager.m_arrGlobalLMG[ oldGlobalAnimationId ].Release();
		}
	}
	else
	{
		localAnimationId = m_arrAnimations.size();

		ModelAnimationHeader animationHeader;
		animationHeader.SetAnimName( animationName );
		animationHeader.m_nGlobalAnimId = globalAnimationLmgId;
		animationHeader.m_nAssetType = LMG_File;

		g_AnimationManager.m_arrGlobalLMG[ globalAnimationLmgId ].AddRef();
		
		m_arrAnimations.push_back( animationHeader );

		StoreAnimName( animationHeader.m_CRC32Name, animationName );

		m_AnimationHashMap.InsertValue( animationHeader.m_CRC32Name, localAnimationId );
	}

	return localAnimationId;
}


int CAnimationSet::LoadLMGFromMemory(const char* resourceName, const char* szAnimName, XmlNodeRef xmlData)
{
	CLoaderLMG loader( this );
	LMGLoadResult result = loader.Load( resourceName, szAnimName, xmlData );
	if ( result == LMG_LOAD_SUCCESS )
	{
		int32 animationID = GetAnimIDByName( szAnimName );
		VerifyLocomotionGroup( animationID );
	}

	return ( result == LMG_LOAD_SUCCESS )? 0 : -1;
}

//--------------------------------------------------------------------------------


const SAnimationSelectionProperties* CAnimationSet::GetAnimationSelectionProperties(const char* szAnimationName)
{
	int32 localAnimID = GetAnimIDByName(szAnimationName);
	return GetAnimationSelectionProperties(localAnimID);
}

const SAnimationSelectionProperties* CAnimationSet::GetAnimationSelectionProperties( int32 localAnimID )
{
#ifdef USE_SELECTION_PROPERTIES
	if (localAnimID < 0)
		return NULL;

	// Just make sure we have the prop's computed...
	if (!ComputeSelectionProperties(localAnimID))
		return NULL;

	int32 globalAnimID = m_arrAnimations[localAnimID].m_nGlobalAnimId;

	if (m_arrAnimations[localAnimID].m_nAssetType==CAF_File)
		return g_AnimationManager.m_arrGlobalCAF[globalAnimID].m_pSelectionProperties;
	if (m_arrAnimations[localAnimID].m_nAssetType==LMG_File)
		return g_AnimationManager.m_arrGlobalLMG[globalAnimID].m_pSelectionProperties;

	CryFatalError("CryAnimation: Invalid Anim-Type");
#endif

	CryFatalError("GetAnimationSelectionProperties: " );
	return 0;
}




bool CAnimationSet::ComputeSelectionProperties(int32 localAnimID)
{
#ifdef USE_SELECTION_PROPERTIES
	int32 globalAnimID = m_arrAnimations[localAnimID].m_nGlobalAnimId;

	if (m_arrAnimations[localAnimID].m_nAssetType==CAF_File)
	{
		uint32 num = g_AnimationManager.m_arrGlobalCAF.size();
		assert(int32(num)>globalAnimID);
		GlobalAnimationHeaderCAF& globalAnimHeaderCAF = g_AnimationManager.m_arrGlobalCAF[globalAnimID];
		if ( globalAnimHeaderCAF.m_pSelectionProperties!=NULL )
			return true;
		if ( !globalAnimHeaderCAF.IsAssetCreated() )
			return false;
		globalAnimHeaderCAF.AllocateAnimSelectProps();
	}

	if (m_arrAnimations[localAnimID].m_nAssetType==LMG_File)
	{
		uint32 num = g_AnimationManager.m_arrGlobalLMG.size();
		assert(int32(num)>globalAnimID);
		GlobalAnimationHeaderLMG& globalAnimHeaderLMG = g_AnimationManager.m_arrGlobalLMG[globalAnimID];
		if (globalAnimHeaderLMG.m_pSelectionProperties != NULL)
			return true;
		if (!globalAnimHeaderLMG.IsAssetCreated())
			return false;

		globalAnimHeaderLMG.AllocateAnimSelectProps();
	}




#define BLENDCODE(code)		(*(uint32*)code)


	if (m_arrAnimations[localAnimID].m_nAssetType==LMG_File)
	{
		GlobalAnimationHeaderLMG& globalAnimHeaderLMG = g_AnimationManager.m_arrGlobalLMG[globalAnimID];

		// Recurse sub animations first
		uint32 numSubAnims = globalAnimHeaderLMG.m_arrBSAnimations.size();
		for (uint32 i = 0; i < numSubAnims; ++i)
		{
			int32 localAnimIDSub = GetAnimIDByCRC(globalAnimHeaderLMG.m_arrBSAnimations[i].m_animName.m_CRC32);
			assert(localAnimIDSub >= 0);
			if (localAnimIDSub < 0)
				return false;
			if (!ComputeSelectionProperties(localAnimIDSub))
				return false;
		}

		{
			assert(globalAnimHeaderLMG.m_pSelectionProperties!=0);
			globalAnimHeaderLMG.m_pSelectionProperties->m_fStartTravelSpeedMin = 100.0f;
			globalAnimHeaderLMG.m_pSelectionProperties->m_fEndTravelSpeedMin = 100.0f;
		}

		// Then use their limits to expand LMG limits.
		for (uint32 i = 0; i < numSubAnims; ++i)
		{
			int32 localAnimIDSub = GetAnimIDByCRC(globalAnimHeaderLMG.m_arrBSAnimations[i].m_animName.m_CRC32);
			assert(localAnimIDSub >= 0);
			int32 globalAnimIDSub = m_arrAnimations[localAnimIDSub].m_nGlobalAnimId;
			assert(globalAnimIDSub >= 0);
			GlobalAnimationHeaderCAF& globalAnimHeaderSub = g_AnimationManager.m_arrGlobalCAF[globalAnimIDSub];
			globalAnimHeaderLMG.m_pSelectionProperties->expand(globalAnimHeaderSub.m_pSelectionProperties);
		}

		SAnimationSelectionProperties& proceduralProperties = *globalAnimHeaderLMG.m_pSelectionProperties;

		//#ifdef _DEBUG
		proceduralProperties.DebugCapsCode = globalAnimHeaderLMG.m_nSelectionCapsCode;
		//#endif

		if (globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("IROT"))
		{
			proceduralProperties.m_fStartTravelAngleMin = -180.0f;
			proceduralProperties.m_fStartTravelAngleMax = +180.0f;
			proceduralProperties.m_fEndTravelAngleMin = -180.0f;
			proceduralProperties.m_fEndTravelAngleMax = +180.0f;
			proceduralProperties.m_fEndBodyAngleMin = -180.0f;
			proceduralProperties.m_fEndBodyAngleMax = +180.0f;
			proceduralProperties.m_fTravelAngleChangeMin = -180.0f;
			proceduralProperties.m_fTravelAngleChangeMax = +180.0f;
			proceduralProperties.m_fTravelDistanceMin = 0.0f;
			proceduralProperties.m_fTravelDistanceMax = 0.0f;
			proceduralProperties.m_fStartTravelSpeedMin = 0.0f;
			proceduralProperties.m_fStartTravelSpeedMax = 0.0f;
			proceduralProperties.m_fEndTravelSpeedMin = 0.0f;
			proceduralProperties.m_fEndTravelSpeedMax = 0.0f;
			proceduralProperties.m_fEndTravelToBodyAngleMin = -180.0f;
			proceduralProperties.m_fEndTravelToBodyAngleMax = +180.0f;
			proceduralProperties.m_fUrgencyMin = 0.0f;
			proceduralProperties.m_fUrgencyMax = 0.0f;
			proceduralProperties.m_bLocomotion = true;
			proceduralProperties.m_fEndBodyAngleThreshold = 70.0f; // NOTE: This should always be less than the clamping angle, or the turn will never get a chance.
		}

		if (globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("ISTP"))
		{
			proceduralProperties.m_fStartTravelAngleMin = -180.0f;
			proceduralProperties.m_fStartTravelAngleMax = +180.0f;
			proceduralProperties.m_fEndTravelAngleMin = -180.0f;
			proceduralProperties.m_fEndTravelAngleMax = +180.0f;
			proceduralProperties.m_fTravelDistanceMin = 0.3f;
			proceduralProperties.m_fTravelDistanceMax = 1.0f;
			proceduralProperties.m_fEndBodyAngleMin = 0.0f;
			proceduralProperties.m_fEndBodyAngleMax = 0.0f;
			proceduralProperties.m_fTravelAngleChangeMin = -180.0f;
			proceduralProperties.m_fTravelAngleChangeMax = +180.0f;
			proceduralProperties.m_fStartTravelSpeedMin = 0.0f;
			proceduralProperties.m_fStartTravelSpeedMax = 0.5f;
			proceduralProperties.m_fEndTravelSpeedMin = 0.0f;
			proceduralProperties.m_fEndTravelSpeedMax = 0.5f;
			proceduralProperties.m_fEndTravelToBodyAngleMin = -180.0f;
			proceduralProperties.m_fEndTravelToBodyAngleMax = +180.0f;
			proceduralProperties.m_fUrgencyMin = 0.0f;
			proceduralProperties.m_fUrgencyMax = 0.0f;
			proceduralProperties.m_bLocomotion = true;
			proceduralProperties.m_fTravelDistanceThreshold = 0.1f;
		}

		if (globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("I2M_") ||		globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("I2W_"))
		{
			proceduralProperties.m_fStartTravelAngleMin = -180.0f;
			proceduralProperties.m_fStartTravelAngleMax = +180.0f;
			proceduralProperties.m_fEndTravelAngleMin = -180.0f;
			proceduralProperties.m_fEndTravelAngleMax = +180.0f;
			proceduralProperties.m_fEndBodyAngleMin = -180.0f;
			proceduralProperties.m_fEndBodyAngleMax = +180.0f;
			proceduralProperties.m_fTravelAngleChangeMin = -30.0f;
			proceduralProperties.m_fTravelAngleChangeMax = +30.0f;
			proceduralProperties.m_fDurationMin = 1.0f;
			proceduralProperties.m_fDurationMax = 10.0f;
			proceduralProperties.m_fTravelDistanceMin = 1.6f;
			proceduralProperties.m_fTravelDistanceMax = 10.0f;
			proceduralProperties.m_fStartTravelSpeedMin = 0.0f;
			proceduralProperties.m_fStartTravelSpeedMax = 0.8f;
			if (globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("I2W_"))
			{
				proceduralProperties.m_fStartTravelSpeedMax = 0.1f;
				proceduralProperties.m_fUrgencyMin = 0.2f;
				proceduralProperties.m_fUrgencyMax = 0.9f;
				proceduralProperties.m_fTravelDistanceMin = 0.4f;
				proceduralProperties.m_fTravelDistanceMax = 5.0f;
			}
			else
			{
				proceduralProperties.m_fUrgencyMin = 1.0f;
				proceduralProperties.m_fUrgencyMax = 3.0f;
			}
			//proceduralProperties.m_fEndTravelSpeedMin *= Console::GetInst().ca_travelSpeedScaleMin;
			//proceduralProperties.m_fEndTravelSpeedMax *= Console::GetInst().ca_travelSpeedScaleMax;
			proceduralProperties.m_fEndTravelToBodyAngleMin = -30.0f;
			proceduralProperties.m_fEndTravelToBodyAngleMax = +30.0f;
			proceduralProperties.m_bPredicted = true;
			proceduralProperties.m_bGuarded = true;
			proceduralProperties.m_bComplexBodyTurning = false; // These two are not supported anymore and are instead specified in the AG template (optimization).
			proceduralProperties.m_bComplexTravelPath = false;
			proceduralProperties.m_bLocomotion = true;
			//proceduralProperties.m_fTravelDistanceThreshold = 1.0f;
		}

		if (globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("M2I_") ||	globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("W2I_"))
		{
			/*
			proceduralProperties.m_fStartTravelAngleMin = -180.0f;
			proceduralProperties.m_fStartTravelAngleMax = +180.0f;
			proceduralProperties.m_fEndTravelAngleMin = -180.0f;
			proceduralProperties.m_fEndTravelAngleMax = +180.0f;
			proceduralProperties.m_fEndBodyAngleMin = -180.0f;
			proceduralProperties.m_fEndBodyAngleMax = +180.0f;
			*/
			proceduralProperties.m_fStartTravelAngleMin = -5.0f;
			proceduralProperties.m_fStartTravelAngleMax = +5.0f;
			proceduralProperties.m_fEndTravelAngleMin = -5.0f;
			proceduralProperties.m_fEndTravelAngleMax = +5.0f;
			proceduralProperties.m_fEndBodyAngleMin = -5.0f;
			proceduralProperties.m_fEndBodyAngleMax = +5.0f;
			proceduralProperties.m_fTravelAngleChangeMin = -5.0f;
			proceduralProperties.m_fTravelAngleChangeMax = 5.0f;
			proceduralProperties.m_fDurationMin = 1.0f;
			proceduralProperties.m_fDurationMax = 1.0f;
			proceduralProperties.m_fTravelDistanceMin = proceduralProperties.m_fTravelDistanceMax * 1.1f;
			proceduralProperties.m_fTravelDistanceMax = proceduralProperties.m_fTravelDistanceMax * 1.2f;
			//proceduralProperties.m_fStartTravelSpeedMin *= Console::GetInst().ca_travelSpeedScaleMin;
			proceduralProperties.m_fStartTravelSpeedMax *= Console::GetInst().ca_travelSpeedScaleMax * 4.0f;
			proceduralProperties.m_fEndTravelSpeedMin = 0.0f;
			proceduralProperties.m_fEndTravelSpeedMax = 0.5f;

			if (globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("W2I_"))
			{
				proceduralProperties.m_fUrgencyMin = 0.2f;
				proceduralProperties.m_fUrgencyMax = 0.9f;
			}
			else
			{
				proceduralProperties.m_fUrgencyMin = 1.0f;
				proceduralProperties.m_fUrgencyMax = 3.0f;
			}

			proceduralProperties.m_fEndTravelToBodyAngleMin = -5.0f;
			proceduralProperties.m_fEndTravelToBodyAngleMax = +5.0f;
			proceduralProperties.m_bPredicted = true;
			proceduralProperties.m_bGuarded = true;
			proceduralProperties.m_bLocomotion = true;
			//proceduralProperties.m_fTravelDistanceThreshold = 1.0f;
		}

		// WALKS
		if (globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("WALK"))
		{
			proceduralProperties.m_fDurationMin = 0.0f;
			proceduralProperties.m_fDurationMax = 10.0f;
			proceduralProperties.m_fTravelDistanceMin = 0.0f;
			proceduralProperties.m_fTravelDistanceMax = 10.0f;
			//proceduralProperties.m_fStartTravelSpeedMin = 1.0f;
			proceduralProperties.m_fStartTravelSpeedMin *= Console::GetInst().ca_travelSpeedScaleMin * 0.0f;
			proceduralProperties.m_fStartTravelSpeedMax *= Console::GetInst().ca_travelSpeedScaleMax;
			proceduralProperties.m_fEndTravelSpeedMin *= Console::GetInst().ca_travelSpeedScaleMin * 0.0f;
			proceduralProperties.m_fEndTravelSpeedMax *= Console::GetInst().ca_travelSpeedScaleMax;
			proceduralProperties.m_fStartTravelAngleMin = -180.0f;
			proceduralProperties.m_fStartTravelAngleMax = +180.0f;
			proceduralProperties.m_fEndTravelAngleMin = -180.0f;
			proceduralProperties.m_fEndTravelAngleMax = +180.0f;
			proceduralProperties.m_fEndBodyAngleMin = -180.0f;
			proceduralProperties.m_fEndBodyAngleMax = +180.0f;
			proceduralProperties.m_fTravelAngleChangeMin = -180.0f;
			proceduralProperties.m_fTravelAngleChangeMax = +180.0f;
			proceduralProperties.m_fEndTravelToBodyAngleMin = -180.0f;
			proceduralProperties.m_fEndTravelToBodyAngleMax = +180.0f;
			proceduralProperties.m_fUrgencyMin = 0.2f;
			proceduralProperties.m_fUrgencyMax = 0.5f;
			proceduralProperties.m_bLocomotion = true;
		}

		// RUN+SPRINT
		if (globalAnimHeaderLMG.m_nSelectionCapsCode == BLENDCODE("RUN_"))
		{
			f32 fScale = 1.0f; 
			proceduralProperties.m_fDurationMin = 0.0f;
			proceduralProperties.m_fDurationMax = 10.0f;
			proceduralProperties.m_fTravelDistanceMin = 0.0f;
			proceduralProperties.m_fTravelDistanceMax = 10.0f;
			//proceduralProperties.m_fStartTravelSpeedMin = 1.0f;
			proceduralProperties.m_fStartTravelSpeedMin *= Console::GetInst().ca_travelSpeedScaleMin * 0.0f;
			proceduralProperties.m_fStartTravelSpeedMax *= Console::GetInst().ca_travelSpeedScaleMax;
			proceduralProperties.m_fEndTravelSpeedMin *= Console::GetInst().ca_travelSpeedScaleMin * 0.0f;
			//proceduralProperties.m_fEndTravelSpeedMin = max(proceduralProperties.m_fStartTravelSpeedMin, proceduralProperties.m_fEndTravelSpeedMin);
			proceduralProperties.m_fEndTravelSpeedMax *= Console::GetInst().ca_travelSpeedScaleMax;
			proceduralProperties.m_fStartTravelAngleMin = -180.0f*fScale;
			proceduralProperties.m_fStartTravelAngleMax = +180.0f*fScale;
			proceduralProperties.m_fEndTravelAngleMin = -180.0f*fScale;
			proceduralProperties.m_fEndTravelAngleMax = +180.0f*fScale;
			proceduralProperties.m_fEndBodyAngleMin = -180.0f*fScale;
			proceduralProperties.m_fEndBodyAngleMax = +180.0f*fScale;
			proceduralProperties.m_fTravelAngleChangeMin = -180.0f*fScale;
			proceduralProperties.m_fTravelAngleChangeMax = +180.0f*fScale;
			proceduralProperties.m_fEndTravelToBodyAngleMin = -180.0f*fScale;
			proceduralProperties.m_fEndTravelToBodyAngleMax = +180.0f*fScale;
			proceduralProperties.m_fUrgencyMin = 0.9f;
			proceduralProperties.m_fUrgencyMax = 2.0f;
			proceduralProperties.m_bLocomotion = true;
		}
	}
	else 
	{
		GlobalAnimationHeaderCAF& rCAF = g_AnimationManager.m_arrGlobalCAF[globalAnimID];

		if (rCAF.m_pSelectionProperties != NULL)
		{
			// Measure the trajectory of the anim to figure out all the properties.
			SAnimationSelectionProperties& props = *rCAF.m_pSelectionProperties;

			props.m_fDurationMin = rCAF.m_fEndSec - rCAF.m_fStartSec;

			int jointIndex = 0;
			const CModelJoint& modelJoint = m_pModel->m_ModelSkeleton.m_arrModelJoints[jointIndex];

			if (rCAF.IsAssetLoaded()==0)
			{
				g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,0,	"GetSelectionProperties: %s", rCAF.GetFilePath() );
				uint32 nRootCRC32	= m_pModel->m_ModelSkeleton.m_arrModelJoints[0].m_nJointCRC32;
				rCAF.LoadControllersCAF(nRootCRC32);
			}
			IController* pController = rCAF.GetControllerByJointCRC32(modelJoint.m_nJointCRC32);
			if (pController == NULL)
				return false;

			static float samplesPerSecond = 20.0f;
			int sampleCount = max(2, (int)(samplesPerSecond * props.m_fDurationMin));
			float sampleDuration = props.m_fDurationMin / (float)sampleCount;

			// These allocating should maybe be moved outside the loop and reused for all assets, allocated at the maximum size, or implemented as chunks.
			Vec3* locomotionLocatorSamplesPos = new Vec3[sampleCount];
			Quat* locomotionLocatorSamplesOri = new Quat[sampleCount];
			Vec3* locomotionLocatorSamplesMovement = new Vec3[sampleCount];
			Vec3* locomotionLocatorSamplesMovementSmoothed = new Vec3[sampleCount];
			Vec3* locomotionLocatorSamplesBodyDirSmoothed = new Vec3[sampleCount];

			// Evaluate translation & orientation of joint 0 of the animation asset.
			for (int i = 0; i < sampleCount; ++i)
			{
				float t = (float)i / (float)(sampleCount - 1);
				pController->GetP( rCAF.NTime2KTime(t), locomotionLocatorSamplesPos[i]);
				pController->GetO( rCAF.NTime2KTime(t), locomotionLocatorSamplesOri[i]);
			}

			for (int i = 0; i < sampleCount; ++i)
			{
				int i0 = max(i - 1, 0);
				int i1 = min(i + 1, sampleCount - 1);
				locomotionLocatorSamplesMovement[i] = locomotionLocatorSamplesPos[i1] - locomotionLocatorSamplesPos[i0];
				float distance = (float)(i1 - i0);
				if (distance > 0.0f)
					locomotionLocatorSamplesMovement[i] /= distance;
			}

			static int windowSampleCount = 10;
			for (int i = 0; i < sampleCount; ++i)
			{
				locomotionLocatorSamplesBodyDirSmoothed[i] = ZERO;
				locomotionLocatorSamplesMovementSmoothed[i] = ZERO;
				float weightSum = 0.0f;
				for (int j = 0; j < windowSampleCount; ++j)
				{
					int k = CLAMP(i - windowSampleCount/2 + j, 0, sampleCount-1);
					float weight = 1.0f - CLAMP((float)(k - i) / (float)windowSampleCount, 0.0f, 1.0f);
					weightSum += weight;
					locomotionLocatorSamplesMovementSmoothed[i] += locomotionLocatorSamplesMovement[k] * weight;

					Vec3 locomotionLocatorBodyDir = locomotionLocatorSamplesOri[i] * FORWARD_DIRECTION;
					locomotionLocatorSamplesBodyDirSmoothed[i] += locomotionLocatorBodyDir * weight;
				}
				if (weightSum > 0.0f)
				{
					locomotionLocatorSamplesMovementSmoothed[i] /= weightSum;
					locomotionLocatorSamplesBodyDirSmoothed[i] /= weightSum;
				}
			}

			float sampleDurationInv = (sampleDuration > 0.0f) ? 1.0f / sampleDuration : 0.0f;
			props.m_fTravelDistanceMin = (locomotionLocatorSamplesPos[sampleCount-1] - locomotionLocatorSamplesPos[0]).GetLength();
			props.m_fStartTravelSpeedMin = locomotionLocatorSamplesMovementSmoothed[0].GetLength() * sampleDurationInv;
			props.m_fEndTravelSpeedMin = locomotionLocatorSamplesMovementSmoothed[sampleCount-1].GetLength() * sampleDurationInv;
			props.m_fStartTravelAngleMin = DEG2RAD(cry_atan2f(-locomotionLocatorSamplesMovementSmoothed[0].x, locomotionLocatorSamplesMovementSmoothed[0].y));
			props.m_fEndTravelAngleMin = DEG2RAD(cry_atan2f(-locomotionLocatorSamplesMovementSmoothed[sampleCount-1].x, locomotionLocatorSamplesMovementSmoothed[sampleCount-1].y));
			float fStartBodyAngle = DEG2RAD(cry_atan2f(-locomotionLocatorSamplesBodyDirSmoothed[0].x, locomotionLocatorSamplesBodyDirSmoothed[0].y));
			float fEndBodyAngle = DEG2RAD(cry_atan2f(-locomotionLocatorSamplesBodyDirSmoothed[sampleCount-1].x, locomotionLocatorSamplesBodyDirSmoothed[sampleCount-1].y));
			props.m_fEndBodyAngleMin = (fEndBodyAngle - fStartBodyAngle);
			if (props.m_fEndBodyAngleMin < -180.0f)
				props.m_fEndBodyAngleMin += 360.0f;
			if (props.m_fEndBodyAngleMin > 180.0f)
				props.m_fEndBodyAngleMin -= 360.0f;

			if (abs(props.m_fStartTravelAngleMin) < 0.0001f) props.m_fStartTravelAngleMin = 0.0f;
			if (abs(props.m_fEndTravelAngleMin) < 0.0001f) props.m_fEndTravelAngleMin = 0.0f;
			if (abs(props.m_fEndBodyAngleMin) < 0.0001f) props.m_fEndBodyAngleMin = 0.0f;

			props.m_fTravelAngleChangeMin = props.m_fEndTravelAngleMin - props.m_fStartTravelAngleMin;

			props.m_fDurationMax					= props.m_fDurationMin;
			props.m_fStartTravelSpeedMax	= props.m_fStartTravelSpeedMin;
			props.m_fEndTravelSpeedMax		=	props.m_fEndTravelSpeedMin;
			props.m_fStartTravelAngleMax	= props.m_fStartTravelAngleMin;
			props.m_fEndTravelAngleMax		=	props.m_fEndTravelAngleMin;
			props.m_fEndBodyAngleMin			=	props.m_fEndBodyAngleMax;
			props.m_fTravelDistanceMax		=	props.m_fTravelDistanceMin;
			props.m_fTravelAngleChangeMax = props.m_fTravelAngleChangeMin;

			delete[] locomotionLocatorSamplesPos;
			delete[] locomotionLocatorSamplesOri;
			delete[] locomotionLocatorSamplesMovement;
			delete[] locomotionLocatorSamplesMovementSmoothed;
			delete[] locomotionLocatorSamplesBodyDirSmoothed;
		}
	}
#endif

	return true;
}





//----------------------------------------------------------------------------------
//----      check if all animation-assets in a locomotion group are valid       ----
//----------------------------------------------------------------------------------
void CAnimationSet::VerifyLMGs()
{
	uint32 numAnimNames = m_arrAnimations.size();
	for (uint32 i=0; i<numAnimNames; i++)
	{
		VerifyLocomotionGroup(i);
	}
}

LmgAnimationStatus CAnimationSet::IsAnimationValidForLMG(const char* lmgBlendCode, const char* szAnimationName)
{
	uint32 nBC = *(uint32*)lmgBlendCode;
	SCRCName animationNameCRC;
	animationNameCRC.SetName( szAnimationName );

	return IsAnimationValidForLMG(nBC, animationNameCRC);
}

LmgAnimationStatus CAnimationSet::IsAnimationValidForLMG(uint32 lmgBlendCode, const SCRCName& animationName)
{
	if (!LMG::IsValidBlendCode(lmgBlendCode))
	{
		return LMGAS_INVALID_BLEND_CODE;
	}

	int32 id = GetAnimIDByCRC(animationName.m_CRC32);
	if (id < 0)
	{
		return LMGAS_ANIMATION_NOT_IN_ANIMATION_LIST;
	}

	uint32 GlobalAnimationID = m_arrAnimations[id].m_nGlobalAnimId;
	GlobalAnimationHeaderCAF& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID];

	if (rGlobalAnimHeader.IsAssetNotFound() || rGlobalAnimHeader.m_fStartSec<0 || rGlobalAnimHeader.m_fEndSec<0)
	{
		return LMGAS_ASSET_DOESNT_EXIST;
	}

	if (LMG::IsStrafingLMGCode(lmgBlendCode))
	{
		f32 speed = rGlobalAnimHeader.m_fMoveSpeed;
		if (speed<0.1f)
		{
			return LMGAS_ANIMATION_HAS_NO_SPEED;
		}		
	}

	return LMGAS_OK;
}

void CAnimationSet::VerifyLocomotionGroup(int animationID)
{
	bool lmg = (m_arrAnimations[animationID].m_nAssetType==LMG_File);
	if (!lmg)
	{
		return;
	}

	uint32 GlobalAnimationID = m_arrAnimations[animationID].m_nGlobalAnimId;
	GlobalAnimationHeaderLMG& rLMG = g_AnimationManager.m_arrGlobalLMG[GlobalAnimationID];

	if (!rLMG.IsAssetCreated())
	{
		assert(rLMG.m_arrBSAnimations.size()==0);
		rLMG.OnAssetLMG();
		bool r=LMG::ParseXML( this, rLMG ); 
		if (r)
		{
			rLMG.OnAssetCreated();
		}
		else
		{
			rLMG.OnAssetNotFound();
			AnimFileWarning( rLMG.GetFilePath(), "XML-File for LMG not found" );
			return;
		}
	}

	if (rLMG.IsAssetLMG()==0)
	{
		return;
	}

	if (!rLMG.IsAssetCreated())
	{
		return;
	}

	//its a locomotion group
	uint32 numLMG = rLMG.m_arrBSAnimations.size();
	uint32 LMG_OK=numLMG;
	uint32 blendCodeLmg = rLMG.m_nBlendCodeLMG;
	for (uint32 g=0; g<numLMG; g++)
	{
		const SCRCName& aName = rLMG.m_arrBSAnimations[g].m_animName;
		LmgAnimationStatus lmgAnimationStatus = IsAnimationValidForLMG( blendCodeLmg, aName );
		if ( lmgAnimationStatus != LMGAS_OK )
		{
			LMG_OK = 0;

			const char* pDebugName = aName.GetName_DEBUG();
			if ( lmgAnimationStatus == LMGAS_ANIMATION_NOT_IN_ANIMATION_LIST )
			{
				if (Console::GetInst().ca_AnimWarningLevel>0)
					AnimFileWarning(m_pModel->GetModelFilePath(),"locomotion group '%s' is invalid! The animation '%s' is not in the chrparams file.", m_arrAnimations[animationID].GetAnimName(),pDebugName);
			}
			else if ( lmgAnimationStatus == LMGAS_ASSET_DOESNT_EXIST )
			{
				if (Console::GetInst().ca_AnimWarningLevel>0)
					AnimFileWarning(m_pModel->GetModelFilePath(),"locomotion group '%s' is invalid! The the asset for animation '%s' does not exist.", m_arrAnimations[animationID].GetAnimName(),pDebugName);
			}			
		}
	}

	if (LMG_OK)
	{
		rLMG.OnAssetLMGValid();
		LMG::ExtractParameters(this, rLMG);
	}

	uint32 numAssets = rLMG.m_arrBSAnimations.size();
	for (uint32 i=0; i<numAssets; i++)
	{
		const char* pname=rLMG.m_arrBSAnimations[i].m_animName.GetName_DEBUG();
		int32 id=GetAnimIDByCRC(rLMG.m_arrBSAnimations[i].m_animName.m_CRC32);
		
		int32 globalID = GetGlobalIDByAnimID_Fast(id);
		const bool bValidGlobalId = (globalID >= 0);
		
		if (!bValidGlobalId)
		{
			continue;
		}

		uint32 OnDemand=g_AnimationManager.m_arrGlobalCAF[globalID].IsAssetOnDemand();
		if (OnDemand)
		{
			if ( g_AnimationManager.m_arrGlobalCAF[globalID].IsAssetLoaded() )
				g_AnimationManager.UnloadAnimationCAF(g_AnimationManager.m_arrGlobalCAF[globalID]);
		}
	}
}


//---------------------------------------------------------------------------------------
// [artemk]: Helper functions for computing animation derived parameters
//---------------------------------------------------------------------------------------
IController* CAnimationSet::GAH_GetRootController(GlobalAnimationHeaderCAF& rGAH, uint32 globalID, const CCharacterModel* pModel, const char* pAnimName)
{
	const CModelJoint* pRootJoint	= &pModel->m_ModelSkeleton.m_arrModelJoints[0];
	IController* pController = rGAH.GetControllerByJointCRC32(pRootJoint->m_nJointCRC32);
	if (pController==0)
		rGAH.LoadControllersCAF(pRootJoint->m_nJointCRC32);
	pController	= rGAH.GetControllerByJointCRC32(pRootJoint[0].m_nJointCRC32);
	if (pController==0)
	{
		const char* pModelName = m_pModel->GetFilePath();
		g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING,	VALIDATOR_FLAG_FILE, pModelName, "LMG is invalid. Locomotion-asset '%s' not in memory ",pAnimName );

		rGAH.m_nFlags =  rGAH.m_nFlags & (CA_ASSET_LMG_VALID^-1);
		return 0;
	}

	return pController;
}





uint32 CAnimationSet::numMorphTargets() const  
{
	CModelMesh* pModelMesh = m_pModel->GetModelMesh(0);
	if (pModelMesh==0)
		return 0;

	return pModelMesh->m_morphTargets.size();
};

const char* CAnimationSet::GetNameMorphTarget (int nMorphTargetId) 
{
	if (nMorphTargetId< 0)
		return "!NEGATIVE MORPH TARGET ID!";

	if ((int)nMorphTargetId >= m_pModel->GetModelMesh(0)->m_morphTargets.size())
		return "!MORPH TARGET ID OUT OF RANGE!";

	return m_pModel->GetModelMesh(0)->m_morphTargets[nMorphTargetId]->m_name.c_str();
};



// prepares to load the specified number of CAFs by reserving the space for the controller pointers
void CAnimationSet::prepareLoadCAFs (uint32 nReserveAnimations)
{
	uint32 numAnims = m_arrAnimations.size();
	if (numAnims)
		CryFatalError("CryAnimation: CAL-file loaded twice");

	m_arrAnimations.reserve (nReserveAnimations);
}


// prepares to load the specified number of CAFs by reserving the space for the controller pointers
void CAnimationSet::prepareLoadANMs (uint32 nReserveAnimations)
{
	nReserveAnimations += m_arrAnimations.size();
	m_arrAnimations.reserve (nReserveAnimations);
}



SPU_NO_INLINE const ModelAnimationHeader* CAnimationSet::GetModelAnimationHeader(int32 i) const
{
	DEFINE_PROFILER_FUNCTION();

	int32 numAnimation = (int32)m_arrAnimations.size();
	if (i<0 || i>=numAnimation)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"Invalid Animation ID: %d",i);
		return 0;
	}
	return &m_arrAnimations[i];  //the animation-asset exists
}




//----------------------------------------------------------------------------------
// Returns the index of the animation in the set, -1 if there's no such animation
//----------------------------------------------------------------------------------
int CAnimationSet::GetAnimIDByName( const char* szAnimationName) const
{ 
	if (szAnimationName==0)
		return -1;

	//this is probably the slowest function in the system.
	//TODO: needs some heavy optimization in the future 
	if (szAnimationName[0] == '#')
	{	
		//search for morph-names
		return int(m_arrAnimations.size() + m_pModel->m_arrModelMeshes[0].FindMorphTarget(szAnimationName));
	}
	else
	{
		return m_AnimationHashMap.GetValue(szAnimationName);
	}
}

// Returns the given animation name
const char* CAnimationSet::GetNameByAnimID ( int nAnimationId)
{
	if (m_pModel->m_ObjectType==CGA)
	{
		if (nAnimationId < 0)
			return "";
#ifdef STORE_ANIMATION_NAMES
		return m_arrAnimations[nAnimationId].GetAnimName();
#else
		HashToNameMap::const_iterator iter(m_hashToNameMap.find(m_arrAnimations[nAnimationId].m_CRC32Name));
		if (iter != m_hashToNameMap.end())
			return iter->second;
		else
			return "";
#endif
	} 
	else
	{
		if (nAnimationId >=0)
		{
			if (nAnimationId < (int)m_arrAnimations.size())
			{
#ifdef STORE_ANIMATION_NAMES
				return m_arrAnimations[nAnimationId].GetAnimName();
#else
				HashToNameMap::const_iterator iter(m_hashToNameMap.find(m_arrAnimations[nAnimationId].m_CRC32Name));
				if (iter != m_hashToNameMap.end())
					return iter->second;
				else
					return "";
#endif
			}

			nAnimationId -= (int)m_arrAnimations.size();
			if (nAnimationId < (int)m_pModel->m_arrModelMeshes[0].m_morphTargets.size())
				return m_pModel->m_arrModelMeshes[0].m_morphTargets[nAnimationId]->m_name.c_str();
			return "!ANIMATION ID OUT OF RANGE!";
		}
		else
			return "!NEGATIVE ANIMATION ID!";
	}

}

//------------------------------------------------------------------------------
SPU_INDIRECT(CommandBufferExecute(M))
int CAnimationSet::GetAnimIDByCRC(uint32 animationCRC) const
{
	size_t value = m_AnimationHashMap.GetValueCRC(animationCRC);
	SPU_VERBATIM_BLOCK("asm volatile(\"nop\");");
	return value;
}

uint32 CAnimationSet::GetCRCByAnimID(int nAnimationId) const
{
	if ((nAnimationId >=0) && (nAnimationId < (int)m_arrAnimations.size()))
	{
			return m_arrAnimations[nAnimationId].m_CRC32Name;
	}
	else
	{
		return 0;
	}
}

uint32 CAnimationSet::GetFilePathCRCByAnimID(int nAnimationId) const
{
	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim==0)
		return 0;

	uint32 GlobalAnimationID			= anim->m_nGlobalAnimId;
	if (anim->m_nAssetType==CAF_File)
		return g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID].GetFilePathCRC32();
	if (anim->m_nAssetType==AIM_File)
		return g_AnimationManager.m_arrGlobalAIM[GlobalAnimationID].GetFilePathCRC32();
	if (anim->m_nAssetType==LMG_File)
		return g_AnimationManager.m_arrGlobalLMG[GlobalAnimationID].GetFilePathCRC32();

	assert(0);
	return  0;
}


//------------------------------------------------------------------------------

f32 CAnimationSet::GetSpeed(int nAnimationId)
{
	int32 numAnimation = (int32)m_arrAnimations.size();
	if (nAnimationId<0 || nAnimationId>=numAnimation)
	{
		AnimFileWarning(m_pModel->GetModelFilePath(),"illegal animation index used in function GetSpeed '%d'", nAnimationId);
		return -1;
	}

	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim==0)
		return -1;

	uint32 GlobalAnimationID			= anim->m_nGlobalAnimId;
	GlobalAnimationHeaderCAF& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID];
	f32	fDistance = rGlobalAnimHeader.m_fDistance;
	f32	fDuration = rGlobalAnimHeader.m_fEndSec - rGlobalAnimHeader.m_fStartSec;
	f32	msec      = fDistance/fDuration;
	return msec;
}

f32 CAnimationSet::GetSlope(int nAnimationId)
{
	int32 numAnimation = (int32)m_arrAnimations.size();
	if (nAnimationId<0 || nAnimationId>=numAnimation)
	{
		AnimFileWarning(m_pModel->GetModelFilePath(),"illegal animation index used in function GetSpeed '%d'", nAnimationId);
		return 0;
	}

	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim==0)
		return 0;

	uint32 GlobalAnimationID			= anim->m_nGlobalAnimId;
	GlobalAnimationHeaderCAF& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID];
	return rGlobalAnimHeader.m_fSlope;
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

GlobalAnimationHeaderCAF* CAnimationSet::GetGAH_CAF(const char* AnimationName) const
{ 
	int32 subAnimID = GetAnimIDByName(AnimationName);
	if (subAnimID<0)
		return 0; //error -> name not found
	return GetGAH_CAF(subAnimID);
}
GlobalAnimationHeaderAIM* CAnimationSet::GetGAH_AIM(const char* AnimationName)
{ 
	int32 subAnimID = GetAnimIDByName(AnimationName);
	if (subAnimID<0)
		return 0; //error -> name not found
	return GetGAH_AIM(subAnimID);
}

GlobalAnimationHeaderLMG* CAnimationSet::GetGAH_LMG(const char* AnimationName)
{ 
	int32 subAnimID = GetAnimIDByName(AnimationName);
	if (subAnimID<0)
		return 0; //error -> name not found
	return GetGAH_LMG(subAnimID);
}




GlobalAnimationHeaderCAF* CAnimationSet::GetGAH_CAF(int nAnimationId)const
{ 
	int32 numAnimation = (int32)m_arrAnimations.size();
	if ((nAnimationId < 0) || (nAnimationId >= numAnimation))
	{
#if !defined(__SPU__)
		AnimFileWarning(m_pModel->GetModelFilePath(),"illegal animation index '%d'", nAnimationId);
#endif
		return NULL;
	}

	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim == NULL)
		return NULL;
	if (anim->m_nAssetType==CAF_File)
		return &g_AnimationManager.m_arrGlobalCAF[anim->m_nGlobalAnimId];
	return 0;
}

GlobalAnimationHeaderAIM* CAnimationSet::GetGAH_AIM(int nAnimationId)
{ 
	int32 numAnimation = (int32)m_arrAnimations.size();
	if ((nAnimationId < 0) || (nAnimationId >= numAnimation))
	{
#if !defined(__SPU__)
		AnimFileWarning(m_pModel->GetModelFilePath(),"illegal animation index '%d'", nAnimationId);
#endif
		return NULL;
	}

	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim == NULL)
		return NULL;
	if (anim->m_nAssetType==AIM_File)
		return &g_AnimationManager.m_arrGlobalAIM[anim->m_nGlobalAnimId];
	return 0;
}


GlobalAnimationHeaderLMG* CAnimationSet::GetGAH_LMG(int nAnimationId)
{ 
	int32 numAnimation = (int32)m_arrAnimations.size();
	if ((nAnimationId < 0) || (nAnimationId >= numAnimation))
	{
#if !defined(__SPU__)
		AnimFileWarning(m_pModel->GetModelFilePath(),"illegal animation index '%d'", nAnimationId);
#endif
		return NULL;
	}

	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim == NULL)
		return NULL;
	if (anim->m_nAssetType==LMG_File)
		return &g_AnimationManager.m_arrGlobalLMG[anim->m_nGlobalAnimId];
	return 0;
}



//------------------------------------------------------------------------------

Vec2 CAnimationSet::GetMinMaxSpeedAsset_msec(int32 animID )
{ 
	Vec2 MinMax(0,0);
	if (animID<0)
		return MinMax;

	GlobalAnimationHeaderCAF* pGlobalAnimHeaderCAF = GetGAH_CAF(animID);
	if (pGlobalAnimHeaderCAF)
	{
		if (pGlobalAnimHeaderCAF->IsAssetCreated()==0)
		{
			AnimFileWarning(m_pModel->GetModelFilePath(),"CryAnimation\\CAnimationSet::GetMinMaxSpeed_msec(): Animation %d is not loaded", animID);
			return MinMax;
		}
		return Vec2(0,0); 
	}

	GlobalAnimationHeaderLMG* pGlobalAnimHeaderLMG = GetGAH_LMG(animID);
	if (pGlobalAnimHeaderLMG)
	{
		if (pGlobalAnimHeaderLMG->IsAssetCreated()==0)
		{
			AnimFileWarning(m_pModel->GetModelFilePath(),"CryAnimation\\CAnimationSet::GetMinMaxSpeed_msec(): Animation %d is not loaded", animID);
			return MinMax;
		}
		f32 minspeed=+9999.0f;
		f32 maxspeed=-9999.0f;
		uint32 subCount = pGlobalAnimHeaderLMG->m_arrBSAnimations.size();
		assert(subCount < 40);
		for (uint32 i = 0; i < subCount; i++)
		{
			int32 subAnimID = GetAnimIDByCRC(pGlobalAnimHeaderLMG->m_arrBSAnimations[i].m_animName.m_CRC32);
			if (subAnimID<0)
				return Vec2(0,0); 

			GlobalAnimationHeaderCAF* rsubGlobalAnimHeader = GetGAH_CAF(subAnimID);
			if (rsubGlobalAnimHeader == 0)
				return Vec2(0,0); 

			if (minspeed>rsubGlobalAnimHeader->m_fMoveSpeed) 
				minspeed=rsubGlobalAnimHeader->m_fMoveSpeed;
			if (maxspeed<rsubGlobalAnimHeader->m_fMoveSpeed) 
				maxspeed=rsubGlobalAnimHeader->m_fMoveSpeed;
		}
		return Vec2(minspeed,maxspeed); 
	}


	return MinMax; 
}

ILINE f32 Turn2BlendWeight(f32 fDesiredTurnSpeed)
{
	f32 sign = f32(sgn(-fDesiredTurnSpeed));
	f32 tb=2.37f/gf_PI;
	f32 turn_bw = fabsf( (fDesiredTurnSpeed/gf_PI)*tb ) - 0.01f;;
	//	f32 turn_bw = fabsf( fDesiredTurnSpeed/(gf_PI*0.70f) ) - 0.0f;;
	if (turn_bw> 1.0f)	turn_bw=1.0f;
	if (turn_bw< 0.0f)	turn_bw=0.00001f;
	return turn_bw*sign;
}

LMGCapabilities CAnimationSet::GetLMGPropertiesByName( const char* szAnimName, Vec2& vStrafeDirection, f32 fDesiredTurn, f32 fSlope  )
{
	LMGCapabilities lmg_caps;

	int32 nAnimID = GetAnimIDByName( szAnimName );
	if (nAnimID<0)	
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"animation-name '%s' not in CAL-file", szAnimName );
		return lmg_caps;
	}
	//check if the asset exists
	const ModelAnimationHeader* pAnim = GetModelAnimationHeader(nAnimID);
	if (pAnim==0)	
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"animation-asset '%s' not loaded", szAnimName);
		return lmg_caps;
	}

	SParametric *lmgQuery = LMG::BuildRuntimeStructLMG(this, pAnim, nAnimID);
	SParametric lmg = *lmgQuery;
	LMG::FreeRuntimeParametric(lmgQuery);

	/*
	f32 m_turn=0;
	if (lmg.m_params[eMotionParamID_TurnSpeed].initialized)
	m_turn = 1.0f - 2.0f * lmg.m_params[eMotionParamID_TurnSpeed].blendspace.m_fAssetBlend;
	else if (lmg.m_params[eMotionParamID_TurnAngle].initialized)
	m_turn = 1.0f - 2.0f * lmg.m_params[eMotionParamID_TurnAngle].blendspace.m_fAssetBlend;
	*/

	lmg.m_BlendSpace.m_strafe	= vStrafeDirection;
	lmg.m_BlendSpace.m_turn		=	Turn2BlendWeight(fDesiredTurn);
	lmg.m_BlendSpace.m_slope	= fSlope;
	lmg.m_BlendSpace.m_speed	= 0.0f; // Not sure this is needed/used by GetLMGCaps, but it should be cheap enough to do.

	lmg.m_params[eMotionParamID_TurnSpeed].value = fDesiredTurn;

	//	float fColor[4] = {1,1,0,1};
	//	g_pIRenderer->Draw2dLabel( 1,g_YLine, 1.3f, fColor, false,"lmg.m_BlendSpace.m_turn: %f     m_turn: %f",lmg.m_BlendSpace.m_turn,m_turn); 
	//	g_YLine+=0x20;

	lmg_caps = LMG::GetCapabilities(this, lmg );
	return lmg_caps;
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
f32 CAnimationSet::GetIWeightForSpeed(int nAnimationId, f32 Speed)
{
	Vec2 MinMax = GetMinMaxSpeedAsset_msec(nAnimationId);
	if (Speed<MinMax.x)
		return -1;
	if (Speed>MinMax.y)
		return 1;

	f32 speed=(Speed-MinMax.x);
	f32 smax=(MinMax.y-MinMax.x);
	return (speed/smax)*2-1;	
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
f32 CAnimationSet::GetDuration_sec(int nAnimationId)
{
	int32 numAnimation = (int32)m_arrAnimations.size();
	if (nAnimationId<0 || nAnimationId>=numAnimation)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"illegal animation index used in function GetDuration_sec'%d'", nAnimationId);
		return -1;
	}

	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim==0)
		return -1;

	if (anim->m_nAssetType==CAF_File)
	{
		uint32 GlobalAnimationID = anim->m_nGlobalAnimId;
		GlobalAnimationHeaderCAF& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID];
		f32   fDuration = rGlobalAnimHeader.m_fEndSec - rGlobalAnimHeader.m_fStartSec;
		return      fDuration;
	}

	if (anim->m_nAssetType==AIM_File)
	{
		uint32 GlobalAnimationID = anim->m_nGlobalAnimId;
		GlobalAnimationHeaderAIM& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalAIM[GlobalAnimationID];
		f32   fDuration = rGlobalAnimHeader.m_fEndSec - rGlobalAnimHeader.m_fStartSec;
		return      fDuration;
	}

	if (anim->m_nAssetType==LMG_File)
	{
		uint32 GlobalAnimationID = anim->m_nGlobalAnimId;
		GlobalAnimationHeaderLMG& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalLMG[GlobalAnimationID];
		uint32 lmg = rGlobalAnimHeader.IsAssetLMG();
		assert(lmg);
		if (rGlobalAnimHeader.IsAssetLMGValid()==0)
			return 0; 

		f32 fDuration=0;
		uint32 numBS = rGlobalAnimHeader.m_arrBSAnimations.size();
		for (uint32 i=0; i<numBS; i++)
		{
			int32 aid=GetAnimIDByCRC(rGlobalAnimHeader.m_arrBSAnimations[i].m_animName.m_CRC32);      
			assert(aid>=0);
			fDuration += GetDuration_sec(aid);
		}
		return      fDuration/numBS;
	}

	assert(0);
	return 0;
}

uint32 CAnimationSet::GetAnimationFlags(int nAnimationId)
{
	int32 numAnimation = (int32)m_arrAnimations.size();
	if (nAnimationId<0 || nAnimationId>=numAnimation)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"illegal animation index used in function GetAnimationFlags '%d'", nAnimationId);
		return 0;
	}
	const ModelAnimationHeader* pAnimHeader = GetModelAnimationHeader(nAnimationId);
	if (pAnimHeader==0)
		return 0;

	uint32 nGlobalAnimationID			= pAnimHeader->m_nGlobalAnimId;

	if (pAnimHeader->m_nAssetType==CAF_File)
	{
		if (g_AnimationManager.m_arrGlobalCAF[nGlobalAnimationID].IsAssetOnDemand())
			g_AnimationManager.m_arrGlobalCAF[nGlobalAnimationID].m_nFlags |= CA_ASSET_ONDEMAND;
		return g_AnimationManager.m_arrGlobalCAF[nGlobalAnimationID].m_nFlags;
	}
	if (pAnimHeader->m_nAssetType==AIM_File)
		return g_AnimationManager.m_arrGlobalAIM[nGlobalAnimationID].m_nFlags;
	if (pAnimHeader->m_nAssetType==LMG_File)
		return g_AnimationManager.m_arrGlobalLMG[nGlobalAnimationID].m_nFlags;

	assert(0);	
	return 0;
}


uint32 CAnimationSet::GetBlendSpaceCode(int nAnimationId)
{
	int32 numAnimation = (int32)m_arrAnimations.size();
	if (nAnimationId<0 || nAnimationId>=numAnimation)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"illegal animation index used in function GetAnimationFlags '%d'", nAnimationId);
		return 0;
	}

	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim==0)
		return 0;

	uint32 GlobalAnimationID = anim->m_nGlobalAnimId;
	GlobalAnimationHeaderLMG& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalLMG[GlobalAnimationID];
	return rGlobalAnimHeader.m_nBlendCodeLMG;
}


//-------------------------------------------------------------------
//! Returns the given animation's start, in seconds; 0 if the id is invalid
//-------------------------------------------------------------------
f32 CAnimationSet::GetStart (int nAnimationId)
{
	int32 numAnimation = (int32)m_arrAnimations.size();
	if (nAnimationId<0 || nAnimationId>=numAnimation)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"illegal animation index used in function GetStart: '%d'", nAnimationId);
		return 0;
	}


	const ModelAnimationHeader* pAnim = &m_arrAnimations[nAnimationId];
	GlobalAnimationHeaderCAF& rGAH = g_AnimationManager.m_arrGlobalCAF[pAnim->m_nGlobalAnimId];
	return rGAH.m_fStartSec;
}



//-----------------------------------------------------------------------------------------------------
CryAnimationPath CAnimationSet::GetAnimationPath(const char* szAnimationName) 
{
	int AnimID = GetAnimIDByName(szAnimationName);

	return GetAnimationPath(AnimID);
};

CryAnimationPath CAnimationSet::GetAnimationPath( int AnimID )
{
	CryAnimationPath p;
	p.m_key0.SetIdentity();
	p.m_key1.SetIdentity();

	if (AnimID<0)
		return p;

	const ModelAnimationHeader* anim = GetModelAnimationHeader(AnimID);
	if (anim && anim->m_nAssetType==CAF_File)
	{
		GlobalAnimationHeaderCAF& rCAF = g_AnimationManager.m_arrGlobalCAF[anim->m_nGlobalAnimId];
		if (rCAF.IsAssetCreated()==0)
			return p;

		uint32 IsAssetOnDemand = rCAF.IsAssetOnDemand();

		if (rCAF.m_EndLocation.q.w==-1.0f)
		{
			//use the old system, that accesses the asset directly
			//this code-path is only executed from Animation-Graph if there is no IMG-file
			//to avoid it, we need a fix in the AG.
			const CModelJoint* pModelJoint = &m_pModel->m_ModelSkeleton.m_arrModelJoints[0];
			IController* pController = rCAF.GetControllerByJointCRC32(pModelJoint[0].m_nJointCRC32);
			assert(pController);
			if (pController)
			{
				Diag33 scale;

				f32 fKeyTime0=rCAF.NTime2KTime(0);
				pController->GetOPS( fKeyTime0, p.m_key0.q, p.m_key0.t,scale); //the first key should be always identity	
				f32 fKeyTime1=rCAF.NTime2KTime(1);
				pController->GetOPS( fKeyTime1, p.m_key1.q, p.m_key1.t,scale);	

				if (Console::GetInst().ca_AnimWarningLevel > 2)
					g_pISystem->Warning( VALIDATOR_MODULE_ANIMATION,VALIDATOR_WARNING, VALIDATOR_FLAG_FILE,0,	"X-ForceLoadCAF: %f (%f %f %f) : %f %f %f      Name: %s",p.m_key0.q.w,p.m_key0.q.v.x,p.m_key0.q.v.y,p.m_key0.q.v.z,    p.m_key0.t.x,p.m_key0.t.y,p.m_key0.t.z,   rCAF.GetFilePath() );
			}
		}
		else
		{
			if (1)
			{
				//final solution
				p.m_key1 = rCAF.m_StartLocation.GetInverted()*rCAF.m_EndLocation;
			}
			else
			{
				//test solution
				const CModelJoint* pModelJoint = &m_pModel->m_ModelSkeleton.m_arrModelJoints[0];
				IController* pController = rCAF.GetControllerByJointCRC32(pModelJoint[0].m_nJointCRC32);
				assert(pController);
				if (pController)
				{
					Diag33 scale;

					f32 fKeyTime0=rCAF.NTime2KTime(0);
					pController->GetOPS( fKeyTime0, p.m_key0.q, p.m_key0.t,scale); //the first key should be always identity	
					uint32 IsEquivalent1 = QuatT::IsEquivalent(p.m_key0, QuatT(IDENTITY));
					if (IsEquivalent1==0)
						CryFatalError("CryAnimation: not identity: %s", rCAF.GetFilePath() );

					f32 fKeyTime1=rCAF.NTime2KTime(1);
					pController->GetOPS( fKeyTime1, p.m_key1.q, p.m_key1.t,scale);	

					QuatT endkey = rCAF.m_StartLocation.GetInverted()*rCAF.m_EndLocation;
					uint32 IsEquivalent2 = QuatT::IsEquivalent(endkey, p.m_key1);
					if (IsEquivalent2==0)
						CryFatalError("CryAnimation: not identity: %s", rCAF.GetFilePath() );

					p.m_key1=endkey;
				}
			}			
		}
	}
	return p;
}

const QuatT& CAnimationSet::GetAnimationStartLocation(const char* szAnimationName) 
{
	int32 AnimID = GetAnimIDByName(szAnimationName);

	return GetAnimationStartLocation(AnimID);
}


const QuatT& CAnimationSet::GetAnimationStartLocation( int32 AnimID )
{
	static QuatT DefaultStartLocation; 
	DefaultStartLocation.SetIdentity();

	if (AnimID<0)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"animation-name not in cal-file: %s",GetNameByAnimID(AnimID));
		return DefaultStartLocation;
	}

	const ModelAnimationHeader* anim = GetModelAnimationHeader(AnimID);
	if (anim==0)
		return DefaultStartLocation;

	uint32 GlobalAnimationID = anim->m_nGlobalAnimId;
	GlobalAnimationHeaderCAF& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID];
	return  rGlobalAnimHeader.m_StartLocation;
}

const QuatT& CAnimationSet::GetJointStartLocation(const char* szAnimationName, int32 jointCRC32) 
{	
	static QuatT DefaultStartLocation; 
	DefaultStartLocation.SetIdentity();

	int32 AnimID = GetAnimIDByName(szAnimationName);
	if (AnimID<0)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"animation-name not in cal-file: %s",szAnimationName);
		return DefaultStartLocation;
	}

	const ModelAnimationHeader* anim = GetModelAnimationHeader(AnimID);
	if (anim==0)
		return DefaultStartLocation;

	uint32 GlobalAnimationID = anim->m_nGlobalAnimId;
	GlobalAnimationHeaderCAF& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID];
	IController* pController = rGlobalAnimHeader.GetControllerByJointCRC32(jointCRC32);
	if (pController==0)
		return DefaultStartLocation;
	pController->GetOP( rGlobalAnimHeader.NTime2KTime(0), DefaultStartLocation.q, DefaultStartLocation.t);

	return DefaultStartLocation;
};

const char* CAnimationSet::GetFilePathByName (const char* szAnimationName) const
{
	int32 AnimID = GetAnimIDByName(szAnimationName);
	if (AnimID<0)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"animation-name not in cal-file: %s",szAnimationName);
		return 0;
	}
	const ModelAnimationHeader* anim = GetModelAnimationHeader(AnimID);
	if (anim==0)
		return 0;

	uint32 GlobalAnimationID			= anim->m_nGlobalAnimId;
	if (anim->m_nAssetType==CAF_File)
		return g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID].GetFilePath();
	if (anim->m_nAssetType==AIM_File)
		return g_AnimationManager.m_arrGlobalAIM[GlobalAnimationID].GetFilePath();
	if (anim->m_nAssetType==LMG_File)
		return g_AnimationManager.m_arrGlobalLMG[GlobalAnimationID].GetFilePath();

	assert(0);
	return  0;
};

const char* CAnimationSet::GetFilePathByID(int nAnimationId) const
{
	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim==0)
		return 0;

	uint32 GlobalAnimationID			= anim->m_nGlobalAnimId;
	if (anim->m_nAssetType==CAF_File)
		return g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID].GetFilePath();
	if (anim->m_nAssetType==AIM_File)
		return g_AnimationManager.m_arrGlobalAIM[GlobalAnimationID].GetFilePath();
	if (anim->m_nAssetType==LMG_File)
		return g_AnimationManager.m_arrGlobalLMG[GlobalAnimationID].GetFilePath();

	assert(0);
	return  0;
};





int32 CAnimationSet::GetGlobalIDByName(const char* szAnimationName)
{
	int32 AnimID = GetAnimIDByName(szAnimationName);
	if (AnimID<0)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"animation-name not in cal-file: %s",szAnimationName);
		return -1;
	}
	const ModelAnimationHeader* anim = GetModelAnimationHeader(AnimID);
	if (anim==0)
		return -1;

	return (int32)anim->m_nGlobalAnimId;
}
int32 CAnimationSet::GetGlobalIDByAnimID(int nAnimationId)
{
	const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimationId);
	if (anim==0)
		return -1;
	return (int32)anim->m_nGlobalAnimId;
}


//if the return-value is positive then the closest-quaternion is at key[0...1]
f32 CAnimationSet::GetClosestQuatInChannel(const char* szAnimationName,int32 JointID, const Quat& reference)
{
	int32 numJoints = m_pModel->m_ModelSkeleton.m_arrModelJoints.size();
	if (JointID<0 || JointID>=numJoints)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"illegal joint index: %d",JointID);
		return -1; // wrong animation name
	}

	int32 AnimID = GetAnimIDByName(szAnimationName);
	if (AnimID<0)
	{
		if (Console::GetInst().ca_AnimWarningLevel>0)
			AnimFileWarning(m_pModel->GetModelFilePath(),"animation-name not in cal-file: %s",szAnimationName);
		return -1; // wrong animation name
	}
	const ModelAnimationHeader* anim = GetModelAnimationHeader(AnimID);
	if (anim==0)
		return -1; //asset does not exist

	f32 smalest_dot=0.0f;
	f32 closest_key=-1;

	uint32 GlobalAnimationID = anim->m_nGlobalAnimId;
	GlobalAnimationHeaderCAF& rGlobalAnimHeader = g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID];
	f32	fDuration = rGlobalAnimHeader.m_fEndSec - rGlobalAnimHeader.m_fStartSec;
	f32 timestep = SECONDS_PER_TICK/fDuration;


	IController* pController = g_AnimationManager.m_arrGlobalCAF[GlobalAnimationID].GetControllerByJointCRC32(m_pModel->m_ModelSkeleton.m_arrModelJoints[JointID].m_nJointCRC32);//m_pModel->m_ModelSkeleton.m_arrModelJoints[JointID].m_arrControllersMJoint[AnimID];
	if (pController)
	{
		for (f32 t=0; t<1.0f; t=t+timestep)
		{
			//NOTE: all quternions in the channel are relative to the parent, which means they are usually close to identity

			Quat keyquat;	pController->GetO( rGlobalAnimHeader.NTime2KTime(t), keyquat);	


			f32 cosine=fabsf(keyquat|reference);
			if (smalest_dot<cosine)
			{
				smalest_dot=cosine;
				closest_key=t;
			}
		}
	}

	return closest_key*fDuration;
}

void CAnimationSet::Clear()
{
	// now there are no controllers referred to by this object, we can release the animations
	uint32 numAnimations = m_arrAnimations.size();
	for (uint32 nAnimId=0; nAnimId<numAnimations; nAnimId++)
	{
		const ModelAnimationHeader* anim = GetModelAnimationHeader(nAnimId);
		if (anim)
		{
			if (anim->m_nAssetType==CAF_File)
			{
				GlobalAnimationHeaderCAF& rCAF = g_AnimationManager.m_arrGlobalCAF[anim->m_nGlobalAnimId];
				g_AnimationManager.AnimationReleaseCAF(rCAF);
			}
			if (anim->m_nAssetType==AIM_File)
			{
				GlobalAnimationHeaderAIM& rAIM = g_AnimationManager.m_arrGlobalAIM[anim->m_nGlobalAnimId];
				g_AnimationManager.AnimationReleaseAIM(rAIM);
			}
			if (anim->m_nAssetType==LMG_File)
			{
				GlobalAnimationHeaderLMG& rLMG = g_AnimationManager.m_arrGlobalLMG[anim->m_nGlobalAnimId];
				g_AnimationManager.AnimationReleaseLMG(rLMG);
			}

		}
	}
	m_arrAnimations.clear();
	m_AnimationHashMap.Clear();
	m_hashToNameMap.clear();
}

uint32 CAnimationSet::GetAnimationSize(const uint32 nAnimationId ) const
{
	assert(nAnimationId >= 0);
	assert((int)nAnimationId < m_arrAnimations.size());
	const ModelAnimationHeader& header = m_arrAnimations[nAnimationId];

	int32 globalID = header.m_nGlobalAnimId;
	if (globalID < 0)
		return 0;

	switch (header.m_nAssetType)
	{
	case LMG_File:
		return g_AnimationManager.m_arrGlobalLMG[globalID].SizeOfLMG();
	case CAF_File:
		return g_AnimationManager.m_arrGlobalCAF[globalID].SizeOfCAF(true);
	default:
		return 0;
	}
}

uint32 CAnimationSet::GetTotalPosKeys(const uint32 nAnimationId ) const
{
	assert(nAnimationId >= 0);
	assert((int)nAnimationId < m_arrAnimations.size());
	const ModelAnimationHeader& header = m_arrAnimations[nAnimationId];

	int32 globalID = header.m_nGlobalAnimId;

	if (header.m_nAssetType == CAF_File)
	{
		assert(globalID >= 0);
		assert(globalID < g_AnimationManager.m_arrGlobalCAF.size());
		return g_AnimationManager.m_arrGlobalCAF[globalID].GetTotalPosKeys();
	}
	return 0;
}

uint32 CAnimationSet::GetTotalRotKeys(const uint32 nAnimationId ) const
{
	assert(nAnimationId >= 0);
	assert((int)nAnimationId < m_arrAnimations.size());
	const ModelAnimationHeader& header = m_arrAnimations[nAnimationId];

	int32 globalID = header.m_nGlobalAnimId;

	if (header.m_nAssetType == CAF_File)
	{
		assert(globalID >= 0);
		assert(globalID < g_AnimationManager.m_arrGlobalCAF.size());
		return g_AnimationManager.m_arrGlobalCAF[globalID].GetTotalRotKeys();
	}
	return 0;
}

bool CAnimationSet::ExportCAF2HTR(const char* szAnimationName, const char* savePath, const ISkeletonAnim* pISkeletonAnim)
{
	GlobalAnimationHeaderCAF* pCAFHead = NULL;
	pCAFHead = GetGAH_CAF(szAnimationName);

	GlobalAnimationHeaderLMG* pLMGHead = NULL;
	pLMGHead = GetGAH_LMG(szAnimationName);

	if(pCAFHead)
		return pCAFHead->Export2HTR(szAnimationName, savePath, m_pModel);

	if(pLMGHead)
	{
		int32 nAnimID = GetAnimIDByName( szAnimationName );
		if (nAnimID<0)	
		{
			AnimFileWarning(m_pModel->GetModelFilePath(),"animation-name '%s' not in CAL-file", szAnimationName );
			return 0; //fail
		}
		const ModelAnimationHeader* pAnim = GetModelAnimationHeader(nAnimID);
		if (pAnim==0)
			return 0; //fail
		return pLMGHead->Export2HTR(szAnimationName, savePath, m_pModel,(CSkeletonAnim*)pISkeletonAnim);
	}
	return false;

}
const char* CAnimationSet::GetDBAFileName(const uint32 nAnimationId) const
{
	assert(nAnimationId >= 0);
	assert((int)nAnimationId < m_arrAnimations.size());
	const ModelAnimationHeader& header = m_arrAnimations[nAnimationId];

	int32 globalID = header.m_nGlobalAnimId;
	if (header.m_nAssetType == CAF_File)
	{
		assert(globalID >= 0);
		assert(globalID < g_AnimationManager.m_arrGlobalCAF.size());
		return g_pCharacterManager->GetDBAFilenameByGlobalID(globalID);
	}
	else
		return NULL;
}

bool CAnimationSet::IsStandupAnim(const char* szAnimName, CryFixedStringT<256>& typeName) const
{ 
	bool bStandup = false;

	assert(szAnimName);
	if (szAnimName)
	{
		bStandup = _strnicmp(szAnimName, "standup", 7) == 0;
		if (bStandup)
		{
			const char* ptr = NULL;
			for(ptr=szAnimName+8; *ptr && _strnicmp(ptr,"back",4) && _strnicmp(ptr,"stomach",7) && _strnicmp(ptr,"side",4); ++ptr);
			typeName.assign(szAnimName+8, ptr-szAnimName-8);
		}
	}

	return bStandup;
}

void CAnimationSet::ProcessStandupAnim(const char* szType, int nLocalAnimId2)
{
	assert(szType);
	if (szType)
	{
		int i=0;
		for(; i<(int)m_arrStandupAnimTypes.size() && _stricmp(m_arrStandupAnimTypes[i],szType)<0; i++);
		if (i>=(int)m_arrStandupAnimTypes.size() || _stricmp(m_arrStandupAnimTypes[i],szType)) 
		{
			m_arrStandupAnimTypes.insert(m_arrStandupAnimTypes.begin()+i, szType);
			m_arrStandupAnims.insert(m_arrStandupAnims.begin()+i, DynArray<int>());
		}
		m_arrStandupAnims[i].push_back(nLocalAnimId2);
	}
}


size_t CAnimationSet::SizeOfAnimationSet() const
{
	size_t nSize=sizeof(CAnimationSet);

	nSize += m_arrAnimations.get_alloc_size();
	uint32 numAnimations = m_arrAnimations.size();
	for (uint32 i=0; i<numAnimations; i++)
		nSize += m_arrAnimations[i].SizeOfAnimationHeader();

	nSize += m_arrStandupAnims.get_alloc_size();
	uint32 numStanUp = m_arrStandupAnims.size();
	for (uint32 i=0; i<numStanUp; i++)
		nSize += m_arrStandupAnims[i].get_alloc_size();

	nSize += m_arrStandupAnimTypes.get_alloc_size();
	uint32 numTypes = m_arrStandupAnimTypes.size();
	for (uint32 i=0; i<numTypes; i++)
		nSize += m_arrStandupAnimTypes[i].capacity();

	nSize += m_AnimationHashMap.GetAllocMemSize();

	for (FacialAnimationSet::const_iterator it = m_facialAnimations.begin(), end = m_facialAnimations.end();	it != end; ++it) 
	{
		nSize += it->name.capacity(); 
		nSize += it->path.capacity();
	}

	for (HashToNameMap::const_iterator iter(m_hashToNameMap.begin()), end(m_hashToNameMap.end()); iter != end; ++iter)
	{
		nSize += sizeof(HashToNameMap::key_type);
		nSize += iter->second.capacity();
	}

	return nSize;
}

void CAnimationSet::GetMemoryUsage(ICrySizer *pSizer) const
{
	pSizer->AddObject( m_arrAnimations );	
	pSizer->AddObject( m_arrStandupAnims );
	pSizer->AddObject( m_arrStandupAnimTypes );	
	pSizer->AddObject( m_AnimationHashMap );
	pSizer->AddObject( m_facialAnimations );
	pSizer->AddObject( m_hashToNameMap );
}
